/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package project1;

import java.util.HashMap;

/**
 *
 * @author asdf
 */
public class BlockLengthConstraint implements Constraint {

    private int[] assignments;
    int n = SetUp.getInstance().getLengthOfSchedule() * SetUp.getInstance().getNumberOfEmployees();
    int maxdistance;

    public BlockLengthConstraint() {
        maxdistance = 0;
        for (int k = 0; k < SetUp.getInstance().getNumberOfShifts(); k++) {
            if (SetUp.getInstance().getMinLengthOfBlocks()[k] > maxdistance) {
                maxdistance = SetUp.getInstance().getMinLengthOfBlocks()[k];
            }
        }
    }

    @Override
    public synchronized boolean check(int[] assignments, int variableIndex, int valueIndex) {
        this.assignments = assignments;

        int[] minLengthOfBlocks = SetUp.getInstance().getMinLengthOfBlocks();
        int[] maxLengthOfBlocks = SetUp.getInstance().getMaxLengthOfBlocks();
        int maxWork = SetUp.getInstance().getMaxLengthOfWorkBlocks();
        int maxOff = SetUp.getInstance().getMaxLengthOfDayOffBlocks();
        int minWork = SetUp.getInstance().getMinLengthOfWorkBlocks();
        int minOff = SetUp.getInstance().getMinLengthOfDayOffBlocks();


        {//check max
            int max = isWorkShift(valueIndex) ? maxLengthOfBlocks[valueIndex] : maxOff;

            boolean inBlockLeft = true;
            boolean inBlockRight = true;
            boolean inWorkBlockLeft = isWorkShift(valueIndex);
            boolean inWorkBlockRight = isWorkShift(valueIndex);
            int leftCount = 0;
            int rightCount = 0;
            int leftWCount = 0;
            int rightWCount = 0;

            int h = java.lang.Math.min(java.lang.Math.max(maxOff, maxWork), n / 2);
            for (int i = 1; i <= h; i++) {
                if (inBlockLeft) {
                    if (isValueAssignedAt(valueIndex, variableIndex - i)) {
                        leftCount++;
                    } else {
                        inBlockLeft = false;
                    }
                }
                if (inWorkBlockLeft) {
                    if (isWorkShiftAssignedAt(variableIndex - i)) {
                        leftWCount++;
                    } else {
                        inWorkBlockLeft = false;
                    }
                }
                if (inBlockRight) {
                    if (isValueAssignedAt(valueIndex, variableIndex + i)) {
                        rightCount++;
                    } else {
                        inBlockRight = false;
                    }
                }
                if (inWorkBlockRight) {
                    if (isWorkShiftAssignedAt(variableIndex + i)) {
                        rightWCount++;
                    } else {
                        inWorkBlockRight = false;
                    }
                }
                if (!inBlockLeft && !inBlockRight && !inWorkBlockLeft && !inWorkBlockRight) {
                    break;
                }
            }
            if (leftCount + rightCount + 1 > max || leftWCount + rightWCount + 1 > maxWork) {
                return false;
            }
        }//end check max


        //ckeck min for own value
        {
            int min = isWorkShift(valueIndex) ? minLengthOfBlocks[valueIndex] : minOff;

            boolean inBlockLeft = true;
            boolean inBlockRight = true;
            boolean inWorkBlockLeft = isWorkShift(valueIndex);
            boolean inWorkBlockRight = isWorkShift(valueIndex);
            int leftCount = 0;
            int rightCount = 0;
            int leftWCount = 0;
            int rightWCount = 0;

            int h = java.lang.Math.min(java.lang.Math.max(minOff, minWork), n / 2);
            for (int i = 1; i <= h; i++) {
                if (inBlockLeft) {
                    if (!isDifferentValueAssignedAt(valueIndex, variableIndex - i)) {
                        leftCount++;
                    } else {
                        inBlockLeft = false;
                    }
                }
                if (inWorkBlockLeft) {
                    if (!isOffShiftAssignedAt(variableIndex - i)) {
                        leftWCount++;
                    } else {
                        inWorkBlockLeft = false;
                    }
                }
                if (inBlockRight) {
                    if (!isDifferentValueAssignedAt(valueIndex, variableIndex + i)) {
                        rightCount++;
                    } else {
                        inBlockRight = false;
                    }
                }
                if (inWorkBlockRight) {
                    if (!isOffShiftAssignedAt(variableIndex + i)) {
                        rightWCount++;
                    } else {
                        inWorkBlockRight = false;
                    }
                }
                if (!inBlockLeft && !inBlockRight && !inWorkBlockLeft && !inWorkBlockRight) {
                    break;
                }
            }
            if (leftCount + rightCount + 1 < min || (isWorkShift(valueIndex) && leftWCount + rightWCount + 1 < minWork)) {
                return false;
            }

        } //end check min for own value

        //check min for other values
        for (int i = 1; i < maxdistance; i++) {
            if (isDifferentValueAssignedAt(valueIndex, variableIndex - i)) {
                int k = assignments[wrapIndex(variableIndex - i)];
                int min = k < SetUp.getInstance().getNumberOfShifts() ? SetUp.getInstance().getMinLengthOfBlocks()[k] : SetUp.getInstance().getMinLengthOfDayOffBlocks();
                for (int j = 1; j < min; j++) {
                    if (isDifferentValueAssignedAt(k, variableIndex - i - j)) {
                        if (i + j <= min) {
                            return false;
                        }
                        break;
                    }
                }
                break;
            }
        }
        for (int i = 1; i < maxdistance; i++) {
            if (isDifferentValueAssignedAt(valueIndex, variableIndex + i)) {
                int k = assignments[wrapIndex(variableIndex + i)];
                int min = k < SetUp.getInstance().getNumberOfShifts() ? SetUp.getInstance().getMinLengthOfBlocks()[k] : SetUp.getInstance().getMinLengthOfDayOffBlocks();
                for (int j = 1; j < min; j++) {
                    if (isDifferentValueAssignedAt(k, variableIndex + i + j)) {
                        if (i + j <= min) {
                            return false;
                        }
                        break;
                    }
                }
                break;
            }
        }
        //check min work on other indices
        
        //check min work on other indices
        
        int minWorkBlockLength=SetUp.getInstance().getMinLengthOfWorkBlocks();
        if(!isWorkShift(valueIndex)){
            
            //go left
            boolean inWorkBlockLeft=false;
            boolean foundWorkShiftLeft=false;
            int leftWorkBlockSize=0;
            for(int i=1;i<=minWorkBlockLength;i++){
                inWorkBlockLeft=!isOffShiftAssignedAt(variableIndex-i);
                foundWorkShiftLeft=foundWorkShiftLeft||isWorkShiftAssignedAt(variableIndex-i);
                if(inWorkBlockLeft)
                    leftWorkBlockSize++;
                else
                    break;
            }
            if(leftWorkBlockSize>0&&leftWorkBlockSize<minWorkBlockLength&&foundWorkShiftLeft)
                return false;
            
            //go right
            boolean inWorkBlockRight=false;
            boolean foundWorkShiftRight=false;
            int rightWorkBlockSize=0;
            for(int i=1;i<=minWorkBlockLength;i++){
                inWorkBlockRight=!isOffShiftAssignedAt(variableIndex+i);
                foundWorkShiftRight=foundWorkShiftRight||isWorkShiftAssignedAt(variableIndex+i);
                if(inWorkBlockRight)
                    rightWorkBlockSize++;
                else
                    break;
            }
            if(rightWorkBlockSize>0&&rightWorkBlockSize<minWorkBlockLength&&foundWorkShiftRight)
                return false;
        }
            

        return true;
    }

    private boolean isValueAssignedAt(int value, int index) {
        return assignments[wrapIndex(index)] == value;
    }

    private boolean isDifferentValueAssignedAt(int value, int index) {
        return assignments[wrapIndex(index)] != -1 && assignments[wrapIndex(index)] != value;
    }

    private boolean isWorkShiftAssignedAt(int index) {
        return isWorkShift(assignments[wrapIndex(index)]);
    }

    private boolean isOffShiftAssignedAt(int index) {
        return assignments[wrapIndex(index)] != -1 && !isWorkShift(assignments[wrapIndex(index)]);
    }

    private int wrapIndex(int index) {
        if (index < 0) {
            index += n;
        }
        index = index % n;


        return index;
    }

    private boolean isWorkShift(int value) {
        return value != -1 && value != SetUp.getInstance().getNumberOfShifts();
    }
}
